package MainComponent;

import java.io.*;
import java.net.SocketException;
import java.util.*;

import net.fortuna.ical4j.data.*;
import net.fortuna.ical4j.model.*;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.component.*;
import net.fortuna.ical4j.model.property.*;
import net.fortuna.ical4j.util.*;
import dataobj.CourseTO;
import exceptiondates.*;

/***
 * Generate the ICS file with the course information.
 */
public class ICSGenerator {

	private Calendar cal;
	private final net.fortuna.ical4j.model.TimeZone tz;
	private ExceptionDates ed = null;

	/***
	 * Class constructor
	 */
	public ICSGenerator() {
		cal = new Calendar();
		tz = TimeZoneRegistryFactory.getInstance().createRegistry().getTimeZone("Asia/Hong_Kong");
		initCal(cal);
	}

	/***
	 * Return whether the ICS file created successfully.
	 * @param courses	the array list of courses
	 * @param university	the university name
	 * @param locationPath	the location path of the ICS file
	 * @return		if the ICS file created successfully
	 */
	public boolean createICSByCourses(ArrayList<CourseTO> courses, String university, String locationPath) {
		File ics = prepareFile(locationPath);
		ed = prepareExceptionDates(university);

		if (ics == null || ed == null)
			return false;
		else {
			for (CourseTO course : courses){
				VEvent event = getEvent(course, ed);
				if(event==null) return false;
				cal.getComponents().add(event);
			}
			try {
				new CalendarOutputter().output(cal, new FileWriter(ics));
				return true;
			} catch (IOException | ValidationException e) {
				e.printStackTrace();
				return false;
			}
		}
	}

	/***
	 * Initialize the calendar.
	 * @param cal	the calendar
	 */
	private void initCal(Calendar cal) {
		cal.getProperties().add(new ProdId("-//Joseph Chong//CS3343//EN"));
		cal.getProperties().add(Version.VERSION_2_0);
		cal.getProperties().add(CalScale.GREGORIAN);
	}

	/***
	 * Return the event of the day.
	 * @param course	the array list of courses
	 * @param ed	the array list of exception dates
	 * @return		the event
	 */
	private VEvent getEvent(CourseTO course, ExceptionDates ed) {
		VEvent event = new VEvent();
		try {
			event.getProperties().add(new Summary(course.getCode() + "-" + course.getSession() + " - " + course.getName()));	
			event.getProperties().add(new DtStart(new DateTime(course.getStartTime().getTime())));
			event.getProperties().add(new DtEnd(new DateTime(course.getEndTime().getTime())));
			event.getProperties().add(new Location(course.getVenue()));
			event.getProperties().add(new Description("Instructor: " + course.getInstructor() + "\n"+ "Credits: " + course.getCredits()));
			event.getProperties().add(getExDateForCourse(course, ed));
			event.getProperties().add(new UidGenerator("1").generateUid());
			event.getProperties().add(Status.VEVENT_CONFIRMED);	
			//recursive rule
			Recur recur = new Recur("FREQ=WEEKLY;BYDAY="+getRRuleWeekDay(course.getWeekDay()));
			recur.setInterval(1);
			recur.setUntil(new DateTime(course.getEndDate()));
			event.getProperties().add(new RRule(recur));
		} catch (SocketException e) {
			ui.UI.showErrorMessage("Something wrong about UID");
			return null;
		} catch (Exception e) {
			ui.UI.showErrorMessage("Something wrong with the recursion rule, please contact developers to fix it");
			return null;
		}
		
		return event;
	}

	/***
	 * Return the week day in short form.
	 * @param weekDay	the week day in number
	 * @return		the week day in short form
	 */
	private String getRRuleWeekDay(Integer weekDay){
		switch(weekDay){
		case 1: return "SU";
		case 2: return "MO";
		case 3: return "TU";
		case 4: return "WE";
		case 5: return "TH";
		case 6: return "FR";
		case 7: return "SA";
		}
		
		return null;
	}
	
	/***
	 * Prepare the ICS file.
	 * @param locationPath	the file path of the ICS file
	 * @return		the ICS file
	 */
	private File prepareFile(String locationPath) {
		File ics = null;
		try {
			ics = new File(locationPath);
			if (!ics.exists()) {
				ics.createNewFile();
			}
		} catch (IOException e) {
			ui.UI.showErrorMessage("Can't modify file system , please grant more rights to the program");
			return null;
		}

		return ics;
	}

	/***
	 * Prepare the exception dates.
	 * @param university	the university name
	 * @return		the array list of the exception dates
	 */
	private ExceptionDates prepareExceptionDates(String university) {
		ExceptionDates ed = null;
		try {
			ed = (exceptiondates.ExceptionDates) Class.forName("exceptiondates." + university + "ExceptionDates").newInstance();
		} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | SecurityException | ClassNotFoundException e) {
			ui.UI.showErrorMessage("Something wrong with ExceptionDates, please contact developers to fix");
			return null;
		}

		return ed;
	}

	/***
	 * Get the exception dates for the course.
	 * @param course	the course objects
	 * @param ed	the exception dates objects
	 * @return		the exception dates
	 */
	private ExDate getExDateForCourse(CourseTO course, ExceptionDates ed){
		ExDate exdate = new ExDate();
		exdate.setTimeZone(tz);
		
		for(java.util.Date d : ed.getList()){
			exdate.getDates().add(getExceptionDatesWithTime(course.getStartTime(), d));
		}
		
		return exdate;
	}
	
	/***
	 * Prepare excpetion dates for ICS file.
	 * @param date	Date of the courses
	 * @param ed	getExceptionDates 
	 * @return Library Date Time object
	 */
	private DateTime getExceptionDatesWithTime(java.util.Date date, java.util.Date ed){
		int hour = 0; int min = 0;
		java.util.Calendar cal = java.util.Calendar.getInstance();
		//get starttime from course
		cal.setTime(date);
		hour = cal.get(cal.HOUR_OF_DAY);
		min = cal.get(cal.MINUTE);
		//put time into exception dates
		cal.setTime(ed);
		cal.set(cal.HOUR_OF_DAY, hour);
		cal.set(cal.MINUTE, min);
		cal.set(cal.SECOND, 0);
		return new DateTime(cal.getTime());
	}
}
